/*
 * TemplateManager.java
 *
 * Un semplice strato di API che gestiscono la logica specifica del template engine FreeMarker
 * Utilizza una versione "parametrica" del pattern singleton
 * Permette di gestire facilmente layout con un template di outline (fisso)
 *
 */
package it.univaq.iw.jweb.utils;

import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

/**
 *
 * @author IngegneriaDelWeb
 */
public class TemplateManager {

    private Configuration cfg;
    //questa variabile conterrà il template di outline, se presente
    private Template outline = null;
    //questa variabile conterrà dei dati statici che vogliamo siano aggiunti automaticamente a tutti i data model
    private Map default_tpl_data;

    /* Quella che segue è una forma diversa del pattrern singleton.
     * In pratica, non avremo una sola istanza di TemplateManager
     * ma diverse istanze, una per ogni specifico ServletContext.
     * Tuttavia, il singleton garantisce che tutte le servlet in un determinato
     * contesto avranno sempre la stessa istanza di TemplateManager
     */
    //la mappa associerà ad agni ServletContext il suo unico TemplateManager
    private static Map<ServletContext, TemplateManager> instances = new HashMap();

    public synchronized static TemplateManager getInstance(ServletContext c) {
        if (!instances.containsKey(c)) {
            instances.put(c, new TemplateManager(c));
        }
        return instances.get(c);
    }

    private TemplateManager(ServletContext c) {
        cfg = new Configuration();
        //impostiamo l'encoding di default per l'output
        cfg.setDefaultEncoding("ISO-8859-1");
        //impostiamo il gestore degli oggetti - trasformerà in data model i Java beans
        cfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
        //impostiamo la directory (relativa al contesto) da cui caricare i templates
        cfg.setServletContextForTemplateLoading(c, "templates");
        //impostiamo un handler per gli errori nei template - utile per il debug
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
        //formato di default per data/ora
        cfg.setDateTimeFormat("dd/MM/yyyy hh:mm:ss");

        //inizializziamo il contenitore per i dati di default
        default_tpl_data = new HashMap();
    }

    //questo metodo aggiunge una variabile ai default inseriti in ciascun datamodel
    public void setDefaultData(String name, Object value) {
        default_tpl_data.put(name, value);
    }

    //questo metodo restituisce un data model (hash) di base,
    //(qui inizializzato con informazioni di base utili alla gestione dell'outline)
    public Map getDefaultDataModel() {
        Map datamodel = new HashMap();

        //aggiungiamo al data model delle strutture private
        Map private_tpl_data = new HashMap();
        private_tpl_data.put("content_tpl", "empty.ftl.html"); //contenuto di default
        //aggiungiamo la data di compilazione
        private_tpl_data.put("compiled_on", Calendar.getInstance().getTime()); //data di compilazione della pagina
        datamodel.put("private", private_tpl_data);

        //aggiungiamo al template i dati di default
        datamodel.put("defaults", default_tpl_data);

        return datamodel;
    }

    //questo metodo imposta il template utilizzato come outline,
    //cioè all'interno del quale verranno inseriti gli altri contenuti
    //generati dalle servlet. A questo scopo, il template di outline
    //dovrà includere, nel punto opportuno, il template indicato dalla
    //variabile "private.content_tpl"
    public void setOutlineTemplate(String template) throws ServletException {
        if (template != null) {
            try {
                //carichiamo e immagazziniamo il template di outline
                outline = cfg.getTemplate(template + ".ftl.html");
            } catch (IOException e) {
                //se il template richiesto è inesistente, disabilitiamo la gestione dell'outline
                outline = null;
                throw new ServletException("Errore nell'elaborazione del template: " + e.getMessage(), e);
            }
        } else {
            //se il nome del template è nullo, disabilitiamo la gestione dell'outline
            outline = null;
        }
    }

    //questo metodo principale si occupa di chiamare Freemarker e compilare il template
    //se è attivo un template di outline, quello richiesto viene inserito
    //all'interno dell'outline
    public void process(String tplname, Map datamodel, PrintWriter out) throws ServletException {
        Template t;
        //assicuriamoci di avere sempre un data model di default da passare al template
        if (datamodel == null) {
            datamodel = getDefaultDataModel();
        }
        try {
            if (outline == null) {
                //se non c'è un outline, carichiamo semplicemente il template specificato
                t = cfg.getTemplate(tplname + ".ftl.html");
            } else {
                //un template di outline è stato specificato
                //il template da caricare è quindi sempre l'outline...
                t = outline;
                //...e il template specifico per questa pagina viene indicato
                //all'outline tramite una variabile private.content_tpl
                //si suppone che l'outline includa questo secondo template
                ((Map) datamodel.get("private")).put("content_tpl", tplname + ".ftl.html");

                ((Map)datamodel.get("private")).put("search_tpl", "search.ftl.html");
                ((Map)datamodel.get("private")).put("formLogin_tpl", "formLogin.ftl.html");
                ((Map)datamodel.get("private")).put("servizi_tpl", "menuServizi.ftl.html");
            }
            //Freemarker: associamo i dati al template e lo mandiamo in output
            t.process(datamodel, out);
        } catch (IOException e) {
            throw new ServletException("Errore nell'elaborazione del template: " + e.getMessage(), e);
        } catch (TemplateException e) {
            throw new ServletException("Errore nell'elaborazione del template: " + e.getMessage(), e);
        }
    }

    public void processJSON(String tplname, Map datamodel, PrintWriter out) throws ServletException {
        Template t;
        //assicuriamoci di avere sempre un data model di default da passare al template
        if (datamodel == null) {
            datamodel = getDefaultDataModel();
        }
        try {
                //se non c'è un outline, carichiamo semplicemente il template specificato
                t = cfg.getTemplate(tplname);
            //Freemarker: associamo i dati al template e lo mandiamo in output
            t.process(datamodel, out);
        } catch (IOException e) {
            throw new ServletException("Errore nell'elaborazione del template: " + e.getMessage(), e);
        } catch (TemplateException e) {
            throw new ServletException("Errore nell'elaborazione del template: " + e.getMessage(), e);
        }
    }
}
